package org.xhystudy.rpc.consumer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.xhystudy.rpc.annotation.RpcReference;
import org.xhystudy.rpc.config.RpcProperties;
import org.xhystudy.rpc.registry.RegistryType;

import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @description: 消费方后置处理器
 * @Author: Xhy
 * @gitee: https://gitee.com/XhyQAQ
 * @copyright: B站: https://space.bilibili.com/152686439
 * @CreateTime: 2023-04-24 23:33
 */
@Slf4j
@Configuration
public class ConsumerPostProcessor implements ApplicationContextAware, BeanClassLoaderAware,BeanFactoryPostProcessor, EnvironmentAware {

    private ApplicationContext context;

    private ClassLoader classLoader;

    private final Map<String, BeanDefinition> rpcRefBeanDefinitions = new LinkedHashMap<>();

    RpcProperties rpcProperties;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
            String beanClassName = beanDefinition.getBeanClassName();
            if (beanClassName != null) {
                Class<?> clazz = ClassUtils.resolveClassName(beanClassName, this.classLoader);
                ReflectionUtils.doWithFields(clazz, this::parseRpcReference);
            }
        }

        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
        this.rpcRefBeanDefinitions.forEach((beanName, beanDefinition) -> {
            if (context.containsBean(beanName)) {
                throw new IllegalArgumentException("spring context already has a bean named " + beanName);
            }
            registry.registerBeanDefinition(beanName, rpcRefBeanDefinitions.get(beanName));
            log.info("注册 RpcReferenceBean {} 成功.", beanName);
        });
    }


    private void parseRpcReference(Field field) {
        RpcReference annotation = AnnotationUtils.getAnnotation(field, RpcReference.class);
        if (annotation != null) {
            String address = rpcProperties.getRegisterAddr();
            String type = rpcProperties.getRegisterType();
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RpcReferenceBean.class);
            /**
             * 执行init方法
             * 其他使用方式:
             * 1.xml
             * <bean id="myBean" class="org.xhy.MyBean" init-method="init"/>
             * 2.bean
             *  @Bean(initMethod = "init")
             *     public MyBean myBean() {
             *         return new MyBean();
             *     }
             */
            builder.setInitMethodName("init");
            builder.addPropertyValue("interfaceClass", field.getType());
            builder.addPropertyValue("serviceVersion", annotation.serviceVersion());
            builder.addPropertyValue("registryType", type);
            builder.addPropertyValue("registryAddr", address);
            builder.addPropertyValue("timeout", annotation.timeout());
            builder.addPropertyValue("loadBalancerType",annotation.loadBalancerType().toString());
            builder.addPropertyValue("faultTolerantType",annotation.faultTolerantType().toString());
            builder.addPropertyValue("retryCount",annotation.retryCount());
            BeanDefinition beanDefinition = builder.getBeanDefinition();
            rpcRefBeanDefinitions.put(field.getName(), beanDefinition);
        }
    }

    @Override
    public void setEnvironment(Environment environment) {
        String prefix = "rpc.";
        String registerAddr = environment.getProperty(prefix + "register-addr");
        String registerType = environment.getProperty(prefix + "register-type");
        String registerPsw = environment.getProperty(prefix + "register-psw");
        if (ObjectUtils.isEmpty(registerType)){
            registerType = "ZOOKEEPER";
        }
        if (RegistryType.toRegistry(registerType) == null){
            throw new IllegalArgumentException("properties in registerType fail");

        }
        RpcProperties properties = RpcProperties.getInstance();
        properties.setRegisterAddr(registerAddr);
        properties.setRegisterType(registerType);
        properties.setRegisterPsw(registerPsw);
        rpcProperties = properties;
    }
}
